package cn.sc.summer.security.token.store.base;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.sc.summer.constant.model.Result;
import cn.sc.summer.constant.service.Support;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.util.ConvertUtil;
import cn.sc.summer.constant.util.SpringUtilX;
import cn.sc.summer.exception.util.ip.IPUtil;
import cn.sc.summer.redis.redisson.RedissonLock;
import cn.sc.summer.redis.util.RedisHelper;
import cn.sc.summer.security.service.afterlogin.base.BaseAfterUserLogin;
import cn.sc.summer.token.encrypt.TokenUtil;
import cn.sc.summer.token.enums.LoginTypeEnum;
import cn.sc.summer.token.po.AuthorityX;
import cn.sc.summer.token.po.UserDetailX;
import cn.sc.summer.token.po.UserTokenInfo;
import cn.sc.summer.token.properties.TokenMaxProperties;
import cn.sc.summer.token.service.EncryptService;
import cn.sc.summer.token.util.ResponseUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.security.core.Authentication;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 类名：创建token接口信息
 *
 * @author a-xin
 * @date 2024/6/28 17:12
 */
@Slf4j
public abstract class BaseTokenStore implements AuthenticationToken, Support {

    private static final long serialVersionUID = -719849026865564075L;

    /**
     * 一端允许同时登录最大个数
     */
    private static final String TOKEN_MAX_KEY = "max";

    /**
     * 一端登录失效时间
     */
    private static final String EXPIRE_TIME = "expireTime";

    /**
     * 创建token信息
     *
     * @param request        请求体
     * @param response       返回数据载体
     * @param authentication 验证信息
     * @param userDetailX    登录用户信息
     */
    public void creatToken(HttpServletRequest request, HttpServletResponse response, Authentication authentication, UserDetailX userDetailX) throws IOException, InstantiationException, IllegalAccessException {

        String userId = userDetailX.getUserId();

        RedissonLock redissonLock = SpringUtil.getBean(RedissonLock.class);
        //此处添加redisson分布式锁机制，可以使网关和security进行集群部署，并未采用共享session的方式进行会话校验，完全依赖redis的token信息进行校验
        boolean redisLock = redissonLock.tryLock(TokenConstant.LOGIN_REDIS_LOCK_KEY + userId, 2000, 2100, TimeUnit.MILLISECONDS);
        if (redisLock) {

            String token = this.getToken(userId);
            this.store(token, authentication, userDetailX);
            this.cacheUserAuth(userDetailX);

            Map<String, Object> tokenMap = new HashMap<>();
            tokenMap.put(HttpHeaders.AUTHORIZATION.toLowerCase(), token);
            tokenMap.put("remoteAddress", IPUtil.getIpAddr(request));
            tokenMap.put("username", userDetailX.getUsername());

            Map<String, String> headerMap = new HashMap<>();
            headerMap.put(HttpHeaders.AUTHORIZATION, token);

            this.write(request, response, ConvertUtil.convertMap(Result.success(tokenMap)), headerMap);
        } else {
            this.write(request, response, ConvertUtil.convertMap(Result.fail("Login failed, please log back in later！")), new HashMap<>());
        }
    }


    /**
     * 写入返回数据信息
     *
     * @param request   请求体
     * @param response  返回载体
     * @param resultMap 返回信息
     * @param headerMap 返回请求头
     * @throws IOException io异常
     */
    protected void write(HttpServletRequest request, HttpServletResponse response, Map<String, Object> resultMap, Map<String, String> headerMap) throws IOException {
        EncryptService encryptService = SpringUtil.getBean(EncryptService.class);
        String encrypt = encryptService.encrypt(request, ConvertUtil.toJsonString(resultMap));
        ResponseUtil.writeSuccess(response, headerMap, encrypt);
    }

    /**
     * 获取token信息
     *
     * @param userId 用户ID
     * @return token
     */
    protected String getToken(String userId) {
        return TokenUtil.createSimpleToken(userId, LoginTypeEnum.WEB);
    }

    /**
     * token信息存储
     *
     * @param userDetailX    登录用户信息
     */
    protected void cacheUserAuth(UserDetailX userDetailX) {
        String userId = userDetailX.getUserId();
        BaseAfterUserLogin baseAfterUserLogin = SpringUtilX.getSupportBean(BaseAfterUserLogin.class, getSupports(), LoginTypeEnum.WEB.getSlogan());
        Set<AuthorityX> authorityX = baseAfterUserLogin.getUserAuth(userId);
        userDetailX.setAuthorities(authorityX);
    }

    /**
     * 存储token
     *
     * @param token          token信息
     * @param authentication 认证信息
     * @param userDetailX    登录用户信息
     */
    protected void store(String token, Authentication authentication, UserDetailX userDetailX) {

        TokenMaxProperties tokenMaxProperties = SpringUtil.getBean(TokenMaxProperties.class);
        Map<String, Map<String, Integer>> config = tokenMaxProperties.getConfig();
        Map<String, Integer> webConfigMap = config.getOrDefault(getSupports().toLowerCase(), new HashMap<>());
        Integer tokenMax = webConfigMap.getOrDefault(TOKEN_MAX_KEY, 2);
        Integer expireTime = webConfigMap.getOrDefault(EXPIRE_TIME, 4);

        String key = userDetailX.getLoginType() + ":" + userDetailX.getUserId();

        if (RedisHelper.hasKey(key)) {
            Set<ZSetOperations.TypedTuple<Object>> typedTupleDes = RedisHelper.zGetAllDES(key);
            //小于登录设备数，直接添加信息
            if (typedTupleDes != null && CollUtil.isNotEmpty(typedTupleDes) && typedTupleDes.size() >= tokenMax) {
                String value = String.valueOf(RedisHelper.zGetScoreMin(key));
                RedisHelper.del(TokenConstant.ACCESS_TOKEN_PREFIX + value);
                //大于或等于，则将分数最小的进行踢除
                RedisHelper.zRemoveMember(key, value);
            }
        }

        RedisHelper.zAdd(key, token, System.currentTimeMillis() + expireTime * 60 * 60 * 1000);
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        userTokenInfo.setLoginType(userDetailX.getLoginType());
        userTokenInfo.setUserDetailX(JSONObject.toJSONString(userDetailX));
        userTokenInfo.setAuthentication(JSONObject.toJSONString(authentication));
        String checkToken = TokenUtil.createCheckToken();
        userTokenInfo.setCheckToken(checkToken);
        Map<String, Object> userTokenInfoMap = ConvertUtil.convertToMap(userTokenInfo);

        RedisHelper.hmSet(TokenConstant.ACCESS_TOKEN_PREFIX + token,
                userTokenInfoMap,
                expireTime * 60 * 60 * 1000 + 1000,
                TimeUnit.MILLISECONDS);
    }

}
